﻿using Assimp;
using Assimp.Unmanaged;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace lowpolyconv
{
    public struct Vector3
    {
        public float x; public float y; public float z;
        public static Vector3 FromFace(Vector3 p0, Vector3 p1, Vector3 p2)
        {
            var d2 = new Vector3 { x = p0.x - p1.x, y = p0.y - p1.y, z = p0.z - p1.z };
            var d1 = new Vector3 { x = p2.x - p1.x, y = p2.y - p1.y, z = p2.z - p1.z };
            d1 = Normal(d1);
            d2 = Normal(d2);
            return Cross(d1, d2);
        }
        public static float Length(Vector3 v)
        {
            return MathF.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
        }
        public static Vector3 Normal(Vector3 v)
        {
            var len = Length(v);
            return new Vector3() { x = v.x / len, y = v.y / len, z = v.z / len };
        }
        public static Vector3 Cross(Vector3 a, Vector3 b)
        {
            var x = a.y * b.z - b.y * a.z;
            var y = b.x * a.z - a.x * b.z;
            var z = a.x * b.y - b.x * a.y;
            return new Vector3() { x = x, y = y, z = z };
        }
    }
    public struct Vector2
    {
        public float x; public float y;
    }
    public struct Color3
    {
        public float r; public float g; public float b;
    }
    //表示面的结构
    public struct Frag
    {
        //public int vcount;//3 or 4
        public uint v1;
        public uint v2;
        public uint v3;
        //public uint v4;
    }
    //表示顶点中骨骼信息的结构
    public struct BoneInfo
    {
        public int bonecount;
        public int bone1;
        public int bone2;
        public int bone3;
        public int bone4;
        public float weight1;
        public float weight2;
        public float weight3;
        public float weight4;
        public void AddWeight(int boneid, float weight)
        {
            if (bonecount == 0)
            {
                bone1 = boneid;
                weight1 = weight;
            }
            else if (bonecount == 1)
            {
                bone2 = boneid;
                weight2 = weight;
            }
            else if (bonecount == 2)
            {
                bone3 = boneid;
                weight3 = weight;
            }
            else if (bonecount == 3)
            {
                bone4 = boneid;
                weight4 = weight;
            }
            else
            {

                float minweight = 0;
                if (weight2 > 0) minweight = Math.Min(weight1, weight2);
                if (weight3 > 0) minweight = Math.Min(minweight, weight3);
                if (weight4 > 0) minweight = Math.Min(minweight, weight4);

                if (minweight != 0 && minweight < weight)
                {
                    //替换一个
                    if (minweight == weight1)
                    {
                        bone1 = boneid;
                        weight1 = weight;
                    }
                    else if (minweight == weight2)
                    {
                        bone2 = boneid;
                        weight2 = weight;
                    }
                    else if (minweight == weight3)
                    {
                        bone3 = boneid;
                        weight3 = weight;
                    }
                    else if (minweight == weight4)
                    {
                        bone4 = boneid;
                        weight4 = weight;
                    }
                    else
                    {
                        throw new Exception("should not be here.");
                    }
                }


                return;
            }
            bonecount++;
        }
    }

    //一个按照顶点和面的信息，组织的表示模型的数据结构
    public class ObjMesh
    {
        public int GetVcount()
        {
            return pos.Length;
        }
        public Vector3[] pos;
        public Vector3[] normal;
        public Vector2[] uv;
        public Color3[] color;

        //切线 obj 格式 不支持，另行处理
        public Vector3[] tangent;
        //骨骼信息 obj格式 不支持，另行处理
        public BoneInfo[] boneInfos;
        public int GetFragCount()
        {
            return frags.Length;
        }
        public Frag[] frags;


        //平坦化算法（lowpoly）
        public static ObjMesh Flat(ObjMesh src)
        {
            var nmesh = new ObjMesh();
            nmesh.frags = new Frag[src.frags.Length];
            nmesh.pos = new Vector3[src.frags.Length * 3];
            nmesh.uv = null;
            nmesh.normal = null;
            nmesh.color = null;

            //flat 必须要uv 和 normal信息，如果原来没有uv，那么也搞一份
            nmesh.uv = new Vector2[src.frags.Length * 3];
            nmesh.normal = new Vector3[src.frags.Length * 3];
            if (src.color != null)
            {
                nmesh.color = new Color3[src.frags.Length * 3];
            }
            if (src.tangent != null)
            {
                nmesh.tangent = new Vector3[src.frags.Length * 3];
            }
            if (src.boneInfos != null)
            {
                nmesh.boneInfos = new BoneInfo[src.frags.Length * 3];
            }
            for (var i = 0; i < src.frags.Length; i++)
            {
                //if (src.frags[i].vcount != 3)
                //    throw new Exception("must vcount=3");

                //nmesh.frags[i].vcount = 3;
                nmesh.frags[i].v1 = (uint)i * 3 + 0;
                nmesh.frags[i].v2 = (uint)i * 3 + 1;
                nmesh.frags[i].v3 = (uint)i * 3 + 2;
                nmesh.pos[i * 3 + 0] = src.pos[src.frags[i].v1];
                nmesh.pos[i * 3 + 1] = src.pos[src.frags[i].v2];
                nmesh.pos[i * 3 + 2] = src.pos[src.frags[i].v3];
                if (src.uv != null)
                {
                    var uv0 = src.uv[src.frags[i].v1];
                    var uv1 = src.uv[src.frags[i].v2];
                    var uv2 = src.uv[src.frags[i].v3];
                    var uvf = new Vector2();
                    uvf.x = (uv0.x + uv1.x + uv2.x) / 3;
                    uvf.y = (uv0.y + uv1.y + uv2.y) / 3;
                    nmesh.uv[i * 3 + 0] = uvf;
                    nmesh.uv[i * 3 + 1] = uvf;
                    nmesh.uv[i * 3 + 2] = uvf;
                }
                else
                {
                    var uvf = new Vector2() { x = 0, y = 0 };
                    nmesh.uv[i * 3 + 0] = uvf;
                    nmesh.uv[i * 3 + 1] = uvf;
                    nmesh.uv[i * 3 + 2] = uvf;
                }
                if (src.color != null)
                {
                    var c0 = src.color[src.frags[i].v1];
                    var c1 = src.color[src.frags[i].v2];
                    var c2 = src.color[src.frags[i].v3];
                    var cf = new Color3();
                    cf.r = (c0.r + c1.r + c2.r) / 3;
                    cf.g = (c0.g + c1.g + c2.g) / 3;
                    cf.b = (c0.b + c1.b + c2.b) / 3;
                    nmesh.color[i * 3 + 0] = cf;
                    nmesh.color[i * 3 + 1] = cf;
                    nmesh.color[i * 3 + 2] = cf;
                }
                var p0 = nmesh.pos[i * 3 + 0];
                var p1 = nmesh.pos[i * 3 + 1];
                var p2 = nmesh.pos[i * 3 + 2];
                var normal = Vector3.FromFace(p0, p1, p2);
                nmesh.normal[i * 3 + 0] = normal;
                nmesh.normal[i * 3 + 1] = normal;
                nmesh.normal[i * 3 + 2] = normal;
                if (src.tangent != null)
                {
                    var t0 = src.tangent[src.frags[i].v1];
                    var t1 = src.tangent[src.frags[i].v2];
                    var t2 = src.tangent[src.frags[i].v3];
                    var tf = new Vector3();
                    tf.x = (t0.x + t1.x + t2.x) / 3;
                    tf.y = (t0.y + t1.y + t2.y) / 3;
                    tf.z = (t0.z + t1.z + t2.z) / 3;
                    nmesh.tangent[i * 3 + 0] = tf;
                    nmesh.tangent[i * 3 + 1] = tf;
                    nmesh.tangent[i * 3 + 2] = tf;
                }
                if (src.boneInfos != null)
                {
                    var b0 = src.boneInfos[src.frags[i].v1];
                    var b1 = src.boneInfos[src.frags[i].v2];
                    var b2 = src.boneInfos[src.frags[i].v3];
                    nmesh.boneInfos[i * 3 + 0] = b0;
                    nmesh.boneInfos[i * 3 + 1] = b1;
                    nmesh.boneInfos[i * 3 + 2] = b2;
                }
            }
            return nmesh;
        }
        public string SaveToObjString()
        {
            StringBuilder sb = new StringBuilder();

            for (var i = 0; i < GetVcount(); i++)
            {
                sb.Append("v " + pos[i].x + " " + pos[i].y + " " + pos[i].z);

                if (color != null)
                    sb.Append(" " + color[i].r + " " + color[i].g + " " + color[i].b);
                sb.Append("\n");
            }
            if (normal != null)
            {
                for (var i = 0; i < GetVcount(); i++)
                {
                    sb.Append("vn " + normal[i].x + " " + normal[i].y + " " + normal[i].z + "\n");

                }
            }
            if (uv != null)
            {
                for (var i = 0; i < GetVcount(); i++)
                {
                    sb.Append("vt " + uv[i].x + " " + uv[i].y + "\n");

                }
            }
            for (var i = 0; i < GetFragCount(); i++)
            {
                var f1 = (frags[i].v1 + 1);
                var f2 = (frags[i].v2 + 1);
                var f3 = (frags[i].v3 + 1);
                //var f4 = (frags[i].v4 + 1);
                if (uv != null && normal != null)
                {
                    //if (frags[i].vcount == 3)
                    sb.Append("f " + (f1 + "/" + f1 + "/" + f1) + " " + (f2 + "/" + f2 + "/" + f2) + " " + (f3 + "/" + f3 + "/" + f3) + "\n");
                    //else if (frags[i].vcount == 4)
                    //    sb.Append("f " + (f1 + "/" + f1 + "/" + f1) + " " + (f2 + "/" + f2 + "/" + f2) + " " + (f3 + "/" + f3 + "/" + f3) + " " + (f4 + "/" + f4 + "/" + f4) + "\n");
                    //else
                    //    throw new Exception("error frag");
                }
                else if (uv != null)
                {
                    //if (frags[i].vcount == 3)
                    sb.Append("f " + (f1 + "/" + f1) + " " + (f2 + "/" + f2) + " " + (f3 + "/" + f3) + "\n");
                    //else if (frags[i].vcount == 4)
                    //    sb.Append("f " + (f1 + "/" + f1) + " " + (f2 + "/" + f2) + " " + (f3 + "/" + f3) + " " + (f4 + "/" + f4) + "\n");
                    //else
                    //    throw new Exception("error frag");
                }
                else
                {
                    //if (frags[i].vcount == 3)
                    sb.Append("f " + (f1) + " " + (f2) + " " + (f3) + "\n");
                    //else if (frags[i].vcount == 4)
                    //    sb.Append("f " + (f1) + " " + (f2) + " " + (f3) + " " + (f4) + "\n");
                    //else
                    //    throw new Exception("error frag");
                }
            }

            var outstr = sb.ToString();
            return outstr;
        }

        public string SaveTangentsToString()
        {
            if (this.tangent == null) return null;
            StringBuilder sb = new StringBuilder();
            for (var i = 0; i < this.tangent.Length; i++)
            {
                sb.AppendLine("tan " + this.tangent[i].x + " " + this.tangent[i].y + " " + this.tangent[i].z);
            }
            return sb.ToString();
        }
        public string SaveBoneInfosToString()
        {
            if (this.boneInfos == null)
                return null;
            StringBuilder sb = new StringBuilder();
            for (var i = 0; i < this.boneInfos.Length; i++)
            {
                sb.AppendLine("bi ("
                    + this.boneInfos[i].bone1 + "/"
                    + this.boneInfos[i].bone2 + "/"
                    + this.boneInfos[i].bone3 + "/"
                    + this.boneInfos[i].bone4 + ") ("
                    + this.boneInfos[i].weight1 + "/"
                     + this.boneInfos[i].weight2 + "/"
                      + this.boneInfos[i].weight3 + "/"
                       + this.boneInfos[i].weight4 + ")"
                    );
            }
            return sb.ToString();
        }

    }
}
